package com.vois.jack.btmgr.devices.WLCommonBleDev;
import android.bluetooth.BluetoothGatt;
import android.os.Bundle;
import android.os.Message;
import android.os.ParcelUuid;
import android.util.Log;

import com.vois.jack.btmgr.blebase.BleAction;
import com.vois.jack.btmgr.blebase.BleConstant;
import com.vois.jack.btmgr.blebase.BleDevCommonMsg;
import com.vois.jack.btmgr.blebase.BleDevice;
import com.vois.jack.btmgr.blebase.BleDeviceFsm;
import com.vois.jack.btmgr.common.IBtConstants;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public abstract class DefaultWLBleDevice extends BleDevice {
    private static final String TAG = "DefaultWLBleDevice";
    protected static final UUID SERVICE_UUID = UUID.fromString("00004100-0000-1000-8000-00805F9B34FB");
    protected static final UUID KEY_UUID = UUID.fromString("00004101-0000-1000-8000-00805F9B34FB");
    protected static final UUID CMD_UUID = UUID.fromString("00004102-0000-1000-8000-00805F9B34FB");
    protected static final int KEY_DOWN   = 0x01;
    protected static final int KEY_UP     = 0x02;
    protected static final int KEY_PRESS  = 0x11;
    protected static final int NOTIFY_TYPE = 0x01;
    protected static final int INDICATE_TYPE = 0x02;
    protected final Map<Integer, CommnadWrapper> commandMap;

    public class CommnadWrapper {
        public Class<?> handlerClass;
        public String methodName;
    }

    public DefaultWLBleDevice() {
        super();
        commandMap = new HashMap<>();
    }

    @Override
    public void getValidationData() {
        sendCommand(WLBleCommand.BLE_CMD_GET_SEQID, null, 0);
    }

    @Override
    public void getAddress(){
        sendCommand(WLBleCommand.BLE_CMD_GET_ADDRESS, null, 0);
    }

    @Override
    public void setSeqID(String seqID) {
        byte[] content = new byte[16];
        for (int i = 0; i < 16; i++) {
            content[i] = Byte.valueOf(seqID.substring(i * 2, i * 2 + 1));
        }
        sendCommand(WLBleCommand.BLE_CMD_SET_SEQID, content, 16);
    }

    @Override
    public void getVendor() {
        sendCommand(WLBleCommand.BLE_CMD_GET_VENDOR, null, 0);
    }

    @Override
    public void getVersion() {
        sendCommand(WLBleCommand.BLE_CMD_GET_VERSION, null, 0);
    }

    @Override
    public String getProtocolName() {
        return IBtConstants.PROTOCOL_VOIS;
    }

    protected void enableCharacteristicNotification(UUID serviceUUID,
                                                    UUID characteristicUUID,
                                                    boolean enable,
                                                    int notifyType, BleAction.BleActionCallback callback) {
        BleAction bleAction = new BleAction();
        Bundle arg = new Bundle();
        if (notifyType == NOTIFY_TYPE) {
            bleAction.setCmd(BleAction.BleCmd.BLE_CMD_ENABLE_NOTIFICATION);
        }else {
            bleAction.setCmd(BleAction.BleCmd.BLE_CMD_ENABLE_INDICATION);
        }
        arg.putParcelable(BleDeviceFsm.EXTRA_SERVICE_UUID, new ParcelUuid(serviceUUID));
        arg.putParcelable(BleDeviceFsm.EXTRA_CHARACTERISTIC_UUID, new ParcelUuid(characteristicUUID));
        arg.putBoolean(BleDeviceFsm.EXTRA_ENABLE_VALUE, enable);
        bleAction.setCmdArg(arg);
        bleAction.setCallback(callback);

        if (getBleDeviceFsm() != null) {
            getBleDeviceFsm().executeBleAction(bleAction);
        }
    }

    protected void sendKeyEvent(int bleKeyCode, int bleKeyStatus) {
        if (getListener() != null) {
            Message message = Message.obtain();
            Bundle bundle = new Bundle();
            message.setData(bundle);
            if (bleKeyStatus == KEY_DOWN) {
                message.what = BleDevCommonMsg.BLE_DEV_COMMON_KEY_PRESSED_MSG.getValue();
            }else if (bleKeyStatus == KEY_UP) {
                message.what = BleDevCommonMsg.BLE_DEV_COMMON_KEY_RELEASED_MSG.getValue();
            }else if (bleKeyStatus == KEY_PRESS) {
                message.what = BleDevCommonMsg.BLE_DEV_COMMON_KEY_CLICKED_MSG.getValue();
            }
            bundle.putInt(BleConstant.EXTRA_KEY_CODE, bleKeyCode);
            getListener().onMessage(getBluetoothDevice(), message);
        }
    }

    protected boolean onKeyProcess(int keyCode, int keyStatus) {
        int bleKeyCode = BleConstant.KEY_PTT;
        boolean ret = false;

        switch (keyCode) {
            case 0x40: {
                bleKeyCode = BleConstant.KEY_PTT;
                ret = true;
            }
            break;

            case 0x41: {
                bleKeyCode = BleConstant.KEY_PREV;
                ret = true;
            }
            break;

            case 0x42: {
                bleKeyCode = BleConstant.KEY_NEXT;
                ret = true;
            }
            break;

            case 0x43: {
                bleKeyCode = BleConstant.KEY_VOLUME_UP;
                ret = true;
            }
            break;

            case 0x44: {
                bleKeyCode = BleConstant.KEY_VOLUME_DOWN;
                ret = true;
            }
            break;

            case 0x45: {
                bleKeyCode = BleConstant.KEY_REWIND;
                ret = true;
            }
            break;

            case 0x46: {
                bleKeyCode = BleConstant.KEY_AI_VOICE;
                ret = true;
            }
            break;

            case 0x47: {
                bleKeyCode = BleConstant.KEY_SEND_POS;
                ret = true;
            }
            break;

            case 0x48: {
                bleKeyCode = BleConstant.KEY_MUTE_ALL;
                ret = true;
            }
            break;

            case 0x49: {
                bleKeyCode = BleConstant.KEY_PASS_CUR;
                ret = true;
            }
            break;

            case 0x4A: {
                bleKeyCode = BleConstant.KEY_BROADCAST;
                ret = true;
            }
            break;

            case 0x4B: {
                bleKeyCode = BleConstant.KEY_GROUP_1;
                ret = true;
            }
            break;

            case 0x4C: {
                bleKeyCode = BleConstant.KEY_GROUP_2;
                ret = true;
            }
            break;

            case 0x4D: {
                bleKeyCode = BleConstant.KEY_GROUP_3;
                ret = true;
            }
            break;

            case 0x4E: {
                bleKeyCode = BleConstant.KEY_JUMP_TO_CURRENT;
                ret = true;
            }
            break;

            case 0x4F: {
                bleKeyCode = BleConstant.KEY_NAME_CURRENT_SESSION;
                ret = true;
            }
            break;
        }

        if (ret) {
            sendKeyEvent(bleKeyCode, keyStatus);
        }

        return ret;
    }

    protected void registerCommandHandler(int cmd, CommnadWrapper command) {
        if (commandMap != null) {
            commandMap.put(cmd, command);
        }
    }

    protected void unRegisterCommandHandler(int cmd) {
        if (commandMap != null) {
            if (commandMap.containsKey(cmd)) {
                commandMap.remove(cmd);
            }
        }
    }

    protected boolean onCommandProcess(byte[] data) {
        boolean ret = false;
        WLBleCommand cmd = WLBleCommand.valueOf(data[0] & 0xff);
        int cmdContentLen = (data[1] & 0xFF);
        byte[] cmdData = new byte[cmdContentLen];
        System.arraycopy(data, 2, cmdData, 0, cmdContentLen);

        if (commandMap.containsKey(cmd.getValue())) {
            CommnadWrapper commnadWrapper = commandMap.get(cmd.getValue());
            try {
                Log.d(TAG, "onCommandProcess: methodStr:" + commnadWrapper.methodName);
                Log.d(TAG, "onCommandProcess: getClass:" + commnadWrapper.handlerClass.toString());
                Method processCmdMethod = commnadWrapper.handlerClass.getMethod(commnadWrapper.methodName, int.class, byte[].class);
                Boolean processRet = (Boolean)processCmdMethod.invoke(this, cmdContentLen, cmdData);
                ret = processRet;
            }catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
                ret = false;
            }
        }

        switch (cmd) {
            case BLE_CMD_GET_SEQID: {
                if (!ret) {
                    ret = onDefaultGetSeqId(cmdContentLen, cmdData);
                }
            }
            break;

            case BLE_CMD_SET_SEQID: {
                if (!ret) {
                    ret = onDefaultSetSeqId(cmdContentLen, cmdData);
                }
            }
            break;

            case BLE_CMD_GET_ADDRESS: {
                if (!ret) {
                    ret = onDefaultGetAddress(cmdContentLen, cmdData);
                }
            }

            case BLE_CMD_GET_VENDOR: {
                if (!ret) {
                    ret = onDefaultGetVendor(cmdContentLen, cmdData);
                }
            }
            break;

            case BLE_CMD_GET_VERSION: {
                if (!ret) {
                    ret = onDefaultGetVersion(cmdContentLen, cmdData);
                }
            }
            break;
        }

        return ret;
    }

    private boolean onDefaultSetSeqId(int cmdContentLen, byte[] cmdData) {
        int result = cmdData[0] & 0xFF;
        //TODO: notify seq id written result;
        if (getListener() != null) {
            Message message = Message.obtain();
            message.what = BleDevCommonMsg.BLE_DEV_COMMON_SET_SEQID_RESULT.getValue();
            Bundle bundle = new Bundle();
            bundle.putInt(BleConstant.EXTRA_VALUE_RESULT, result);
            message.setData(bundle);
            getListener().onMessage(getBluetoothDevice(), message);
        }

        return true;
    }

    private boolean onDefaultGetSeqId(int cmdContentLen, byte[] cmdData) {
        char[] out = new char[cmdContentLen * 2];
        String seqId;
        final char[] DIGITS_UPPER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
                'E', 'F' };
        if (cmdContentLen >= 16) {
            for (int i = 0, j = 0; i < cmdContentLen; i++) {
                out[j++] = DIGITS_UPPER[(0xF0 & cmdData[i]) >>> 4];
                out[j++] = DIGITS_UPPER[0x0F & cmdData[i]];
            }
            seqId = new String(out);
        }else {
            seqId = "";
        }

        if (getListener() != null) {
            Message message = Message.obtain();
            message.what = BleDevCommonMsg.BLE_DEV_COMMON_VALIDATION_RESULT.getValue();
            Bundle bundle = new Bundle();
            bundle.putString(BleConstant.EXTRA_SEQID, seqId);
            message.setData(bundle);
            getListener().onMessage(getBluetoothDevice(), message);
        }

        return true;
    }

    private boolean onDefaultGetAddress(int cmdContentLen, byte[] cmdData) {
        char[] out = new char[cmdContentLen * 2];
        String address;
        final char[] DIGITS_UPPER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
                'E', 'F' };
        if (cmdContentLen >= 6) {
            for (int i = 0, j = 0; i < cmdContentLen; i++) {
                out[j++] = DIGITS_UPPER[(0xF0 & cmdData[i]) >>> 4];
                out[j++] = DIGITS_UPPER[0x0F & cmdData[i]];
            }
            address = new String(out).toLowerCase();
        }else {
            address = "";
        }

        final BleDeviceListener listener = getListener();
        if (listener != null) {
            Message message = Message.obtain();
            message.what = BleDevCommonMsg.BLE_DEV_COMMON_GET_ADDRESS_RESULT.getValue();
            Bundle bundle = new Bundle();
            bundle.putString(BleConstant.EXTRA_ADDRESS, address);
            message.setData(bundle);
            listener.onMessage(getBluetoothDevice(), message);
        }

        return true;
    }

    private boolean onDefaultGetVendor(int cmdContentLen, byte[] cmdData) {
        String vender = new String(cmdData);
        if (getListener() != null) {
            Message message = Message.obtain();
            message.what = BleDevCommonMsg.BLE_DEV_COMMON_GET_VENDOR_RESULT.getValue();
            Bundle bundle = new Bundle();
            bundle.putString(BleConstant.EXTRA_VENDORID, vender);
            message.setData(bundle);
            getListener().onMessage(getBluetoothDevice(), message);
        }

        return true;
    }

    private boolean onDefaultGetVersion(int cmdContentLen, byte[] cmdData) {
        String version = new String(cmdData);
        if (getListener() != null) {
            Message message = Message.obtain();
            message.what = BleDevCommonMsg.BLE_DEV_COMMON_GET_VERSION_RESULT.getValue();
            Bundle bundle = new Bundle();
            bundle.putString(BleConstant.EXTRA_VERSION, version);
            message.setData(bundle);
            getListener().onMessage(getBluetoothDevice(), message);
        }

        return true;
    }

    @Override
    public void onCharacteristiChanged(UUID serviceUUID, UUID characteristicUUID, byte[] data) {
        if (characteristicUUID.equals(KEY_UUID)) {
            if (data[0] == (byte)0xFF) {
                int keyCode = (data[2] & 0xFF);
                int keyStatus = (data[3] & 0xFF);
                onKeyProcess(keyCode, keyStatus);
            }
        }else if (characteristicUUID.equals(CMD_UUID)) {
            onCommandProcess(data);
        }
    }

    @Override
    public void onReliableWriteCompleted(int status) {

    }

    protected byte[] sendCommand(WLBleCommand cmd, byte[] content, int contentSize) {
        byte[] sendData = new byte[2 + contentSize];
        sendData[0] = (byte)(cmd.getValue() & 0xFF);
        sendData[1] = (byte)(contentSize & 0xFF);
        if (content != null) {
            System.arraycopy(content, 0, sendData, 2, contentSize);
        }

        writeCharacteristicData(SERVICE_UUID, CMD_UUID, sendData, 2 + contentSize, new BleAction.BleActionCallback() {
            @Override
            public void onActionResult(int status, Bundle result) {
                if (status == BluetoothGatt.GATT_SUCCESS) {

                }
            }
        });

        return sendData;
    }

    @Override
    public void onReady() {
        enableCharacteristicNotification(SERVICE_UUID, KEY_UUID, true, NOTIFY_TYPE, new BleAction.BleActionCallback() {
            @Override
            public void onActionResult(int status, Bundle result) {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    enableCharacteristicNotification(SERVICE_UUID, CMD_UUID, true, NOTIFY_TYPE, new BleAction.BleActionCallback() {
                        @Override
                        public void onActionResult(int status, Bundle result) {
                            final BleDeviceListener listener = getListener();
                            if(status == BluetoothGatt.GATT_SUCCESS){
                                if(listener != null){
                                    listener.onReady(getBluetoothDevice());
                                }
                            }
                        }
                    });
                }
            }
        });
    }
}
